/**
 * Title :	jFamilyTree generator
 * Description: jFamilyTree generator
 * Version:	provider
 * Copyright (c) 2001-2008
 * Terms of use:see license agreement at http://treebolic.sourceforge.net/en/license.htm
 * Author: Bernard Bou
 * Company: bsys
 */
package jFamilyTree.generator;

import java.awt.Color;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;

import jFamilyTree.model.IEdge;
import jFamilyTree.model.INode;
import jFamilyTree.model.MenuItem;
import jFamilyTree.model.Model;
import jFamilyTree.model.MountPoint;
import jFamilyTree.model.Utils;

/**
 * Model to document transformer
 * 
 * @author Bernard Bou
 */
public class ModelToDocumentTransformer
{
	static private boolean validate = true;

	/**
	 * Transform model to document
	 * 
	 * @param thisModel
	 *        model
	 * @return DOM document
	 */
	@SuppressWarnings("serial") //$NON-NLS-1$
	public static Document transform(Model thisModel)
	{
		if (thisModel == null)
			return null;

		DocumentBuilder thisBuilder;
		try
		{
			thisBuilder = makeDocumentBuilder();
			Document thisDocument = thisBuilder.newDocument();
			thisDocument.appendChild(thisDocument.createComment("created " + new Date())); //$NON-NLS-1$
			Map<String, Object> theseAttributes = new HashMap<String, Object>()
			{
				/*
				 * (non-Javadoc)
				 * 
				 * @see java.util.Hashtable#put(java.lang.String, java.lang.Object)
				 */
				@Override
				public Object put(String thisKey, Object thisValue)
				{
					if (thisValue == null)
						return null;
					return super.put(thisKey, thisValue);
				}
			};

			// top
			Element thisTopElement = thisDocument.createElement("jFamilyTree"); //$NON-NLS-1$
			theseAttributes.put("toolbar", thisModel.theSettings.theHasToolbarFlag); //$NON-NLS-1$
			theseAttributes.put("statusbar", thisModel.theSettings.theHasStatusbarFlag); //$NON-NLS-1$
			theseAttributes.put("popupmenu", thisModel.theSettings.theHasPopUpMenuFlag); //$NON-NLS-1$
			theseAttributes.put("tooltip", thisModel.theSettings.theHasToolTipFlag); //$NON-NLS-1$
			theseAttributes.put("tooltip-displays-content", thisModel.theSettings.theToolTipDisplaysContentFlag); //$NON-NLS-1$
			theseAttributes.put("focus-on-hover", thisModel.theSettings.theFocusOnHoverFlag); //$NON-NLS-1$
			theseAttributes.put("focus", thisModel.theSettings.theFocus); //$NON-NLS-1$
			theseAttributes.put("xmoveto", thisModel.theSettings.theXMoveTo); //$NON-NLS-1$
			theseAttributes.put("ymoveto", thisModel.theSettings.theYMoveTo); //$NON-NLS-1$
			theseAttributes.put("xshift", thisModel.theSettings.theXShift); //$NON-NLS-1$
			theseAttributes.put("yshift", thisModel.theSettings.theYShift); //$NON-NLS-1$
			setAttributes(thisTopElement, theseAttributes);
			thisDocument.appendChild(thisTopElement);

			// tree
			Element thisTreeElement = thisDocument.createElement("tree"); //$NON-NLS-1$
			theseAttributes.put("backcolor", thisModel.theSettings.theBackColor); //$NON-NLS-1$
			theseAttributes.put("forecolor", thisModel.theSettings.theForeColor); //$NON-NLS-1$
			theseAttributes.put("orientation", thisModel.theSettings.theOrientation); //$NON-NLS-1$
			theseAttributes.put("expansion", thisModel.theSettings.theExpansion); //$NON-NLS-1$
			theseAttributes.put("sweep", thisModel.theSettings.theSweep); //$NON-NLS-1$
			theseAttributes.put("preserve-orientation", thisModel.theSettings.thePreserveOrientationFlag); //$NON-NLS-1$
			theseAttributes.put("fontface", thisModel.theSettings.theFontFace); //$NON-NLS-1$
			theseAttributes.put("fontsize", thisModel.theSettings.theFontSize); //$NON-NLS-1$
			theseAttributes.put("fontsizestep", thisModel.theSettings.theFontSizeStep); //$NON-NLS-1$
			setAttributes(thisTreeElement, theseAttributes);
			thisTopElement.appendChild(thisTreeElement);
			// image
			if (thisModel.theSettings.theBackgroundImage != null)
				thisTreeElement.appendChild(makeImage(thisDocument, thisModel.theSettings.theBackgroundImage));

			// nodes
			Element thisNodesElement = thisDocument.createElement("nodes"); //$NON-NLS-1$
			theseAttributes.put("backcolor", thisModel.theSettings.theNodeBackColor); //$NON-NLS-1$
			theseAttributes.put("forecolor", thisModel.theSettings.theNodeForeColor); //$NON-NLS-1$
			setAttributes(thisNodesElement, theseAttributes);
			thisTreeElement.appendChild(thisNodesElement);

			if (thisModel.theSettings.theDefaultNodeImage != null)
				thisNodesElement.appendChild(makeImage(thisDocument, thisModel.theSettings.theDefaultNodeImage));

			// default.treeedge
			if (thisModel.theSettings.theDefaultTreeEdgeImage != null || thisModel.theSettings.theTreeEdgeColor != null || thisModel.theSettings.theTreeEdgeStyle != null)
			{
				Element thisDefaultTreeEdgeElement = thisDocument.createElement("default.treeedge"); //$NON-NLS-1$
				// color, stroke,toterminator,fromterminator,hidden
				if (thisModel.theSettings.theTreeEdgeColor != null)
					thisDefaultTreeEdgeElement.setAttribute("color", Utils.colorToString(thisModel.theSettings.theTreeEdgeColor)); //$NON-NLS-1$
				if (thisModel.theSettings.theTreeEdgeStyle != null)
					setStyleAttributes(thisDefaultTreeEdgeElement, thisModel.theSettings.theTreeEdgeStyle);
				// image
				if (thisModel.theSettings.theDefaultTreeEdgeImage != null)
					thisDefaultTreeEdgeElement.appendChild(makeImage(thisDocument, thisModel.theSettings.theDefaultTreeEdgeImage));
				thisNodesElement.appendChild(thisDefaultTreeEdgeElement);
			}

			// node iteration
			thisNodesElement.appendChild(makeNode(thisDocument, thisModel.theTree.getRoot()));

			// edges
			List<IEdge> theseEdges = thisModel.theTree.getEdges();
			if (theseEdges != null && !theseEdges.isEmpty())
			{
				Element thisEdgesElement = thisDocument.createElement("edges"); //$NON-NLS-1$
				theseAttributes.put("hyperbolic", thisModel.theSettings.theHyperbolicEdgesFlag); //$NON-NLS-1$
				setAttributes(thisEdgesElement, theseAttributes);
				thisTreeElement.appendChild(thisEdgesElement);

				// default edge
				if (thisModel.theSettings.theDefaultEdgeImage != null || thisModel.theSettings.theEdgeColor != null || thisModel.theSettings.theEdgeStyle != null)
				{
					Element thisDefaultEdgeElement = thisDocument.createElement("default.edge"); //$NON-NLS-1$
					// attributes
					// color, stroke,toterminator,fromterminator,hidden
					if (thisModel.theSettings.theEdgeColor != null)
						thisDefaultEdgeElement.setAttribute("color", Utils.colorToString(thisModel.theSettings.theEdgeColor)); //$NON-NLS-1$
					if (thisModel.theSettings.theEdgeStyle != null)
						setStyleAttributes(thisDefaultEdgeElement, thisModel.theSettings.theEdgeStyle);
					// image
					if (thisModel.theSettings.theDefaultEdgeImage != null)
						thisDefaultEdgeElement.appendChild(makeImage(thisDocument, thisModel.theSettings.theDefaultEdgeImage));
					thisEdgesElement.appendChild(thisDefaultEdgeElement);
				}

				// edge iteration
				for (IEdge thisEdge : theseEdges)
					if (thisEdge.getFrom() != null && thisEdge.getTo() != null)
						thisEdgesElement.appendChild(makeEdge(thisDocument, thisEdge));
			}

			// menu item iteration
			if (thisModel.theSettings.theMenu != null && !thisModel.theSettings.theMenu.isEmpty())
			{
				// menu
				Element thisToolsElement = thisDocument.createElement("tools"); //$NON-NLS-1$
				thisTopElement.appendChild(thisToolsElement);
				Element thisMenuElement = thisDocument.createElement("menu"); //$NON-NLS-1$
				thisToolsElement.appendChild(thisMenuElement);

				for (MenuItem thisMenuItem : thisModel.theSettings.theMenu)
				{
					Element thisMenuItemElement = makeMenuItem(thisDocument, thisMenuItem);
					thisMenuElement.appendChild(thisMenuItemElement);
				}
			}
			return thisDocument;
		}
		catch (ParserConfigurationException thisException)
		{
		}
		return null;
	}

	/**
	 * Make document builder
	 * 
	 * @return document builder
	 * @throws ParserConfigurationException
	 */
	static private DocumentBuilder makeDocumentBuilder() throws ParserConfigurationException
	{
		DocumentBuilderFactory thisFactory = DocumentBuilderFactory.newInstance();
		thisFactory.setCoalescing(true);
		thisFactory.setIgnoringComments(true);
		thisFactory.setNamespaceAware(false);
		thisFactory.setIgnoringElementContentWhitespace(true);
		thisFactory.setValidating(validate);
		return thisFactory.newDocumentBuilder();
	}

	/**
	 * Make node element
	 * 
	 * @param thisDocument
	 *        document
	 * @param thisNode
	 *        node
	 * @return node element
	 */
	static private Element makeNode(Document thisDocument, INode thisNode)
	{
		// mounted node : substitute mounting node
		MountPoint thisMountPoint = thisNode.getMountPoint();
		if (thisMountPoint != null && thisMountPoint instanceof MountPoint.Mounted)
		{
			MountPoint.Mounted thisMountedPoint = (MountPoint.Mounted) thisMountPoint;
			thisNode = thisMountedPoint.theMountingNode;
			thisMountPoint = thisNode.getMountPoint();
		}
		
		Element thisNodeElement = thisDocument.createElement("node"); //$NON-NLS-1$

		// attributes
		// id, backcolor, forecolor
		thisNodeElement.setAttribute("id", thisNode.getId()); //$NON-NLS-1$
		Color thisColor = thisNode.getBackColor();
		if (thisColor != null)
			thisNodeElement.setAttribute("backcolor", Utils.colorToString(thisColor)); //$NON-NLS-1$
		thisColor = thisNode.getForeColor();
		if (thisColor != null)
			thisNodeElement.setAttribute("forecolor", Utils.colorToString(thisColor)); //$NON-NLS-1$
		double thisWeight = thisNode.getWeight();
		if (thisWeight < 0.)
			thisNodeElement.setAttribute("weight", Double.toString(-thisWeight)); //$NON-NLS-1$

		// label
		String thisString = null;
		thisString = thisNode.getLabel();
		if (thisString != null && !thisString.isEmpty())
			thisNodeElement.appendChild(makeLabel(thisDocument, thisString));

		// content
		thisString = thisNode.getContent();
		if (thisString != null && !thisString.isEmpty())
			thisNodeElement.appendChild(makeContent(thisDocument, thisString));

		// treeedge
		String thisEdgeLabel = thisNode.getEdgeLabel();
		String thisEdgeImage = thisNode.getEdgeImageFile();
		Color thisEdgeColor = thisNode.getEdgeColor();
		Integer thisEdgeStyle = thisNode.getEdgeStyle();
		if (thisEdgeLabel != null || thisEdgeImage != null || thisEdgeColor != null || thisEdgeStyle != null)
		{
			Element thisTreeEdgeElement = thisDocument.createElement("treeedge"); //$NON-NLS-1$
			// attributes
			// color, stroke,toterminator,fromterminator,hidden
			if (thisEdgeColor != null)
				thisTreeEdgeElement.setAttribute("color", Utils.colorToString(thisEdgeColor)); //$NON-NLS-1$
			if (thisEdgeStyle != null)
				setStyleAttributes(thisTreeEdgeElement, thisEdgeStyle);
			// label
			if (thisEdgeLabel != null)
				thisTreeEdgeElement.appendChild(makeLabel(thisDocument, thisEdgeLabel));
			// image
			if (thisEdgeImage != null)
				thisTreeEdgeElement.appendChild(makeImage(thisDocument, thisEdgeImage));
			thisNodeElement.appendChild(thisTreeEdgeElement);
		}

		// image
		thisString = thisNode.getImageFile();
		if (thisString != null && !thisString.isEmpty())
			thisNodeElement.appendChild(makeImage(thisDocument, thisString));

		// link
		thisString = thisNode.getLink();
		if (thisString != null && !thisString.isEmpty())
			thisNodeElement.appendChild(makeLink(thisDocument, thisString, thisNode.getTarget()));

		// mount
		if (thisMountPoint != null && thisMountPoint instanceof MountPoint.Mounting)
		{
			MountPoint.Mounting thisMountingPoint = (MountPoint.Mounting) thisMountPoint;
			thisNodeElement.appendChild(makeMountPoint(thisDocument, thisMountingPoint));
		}

		// recurse
		for (INode thisChild : thisNode.getChildren())
			thisNodeElement.appendChild(makeNode(thisDocument, thisChild));

		return thisNodeElement;
	}

	/**
	 * Make edge element
	 * 
	 * @return edge element
	 */
	static private Element makeEdge(Document thisDocument, IEdge thisEdge)
	{
		Element thisEdgeElement = thisDocument.createElement("edge"); //$NON-NLS-1$

		// attributes
		// from,to,color,stroke,toterminator,fromterminator,hidden
		thisEdgeElement.setAttribute("from", thisEdge.getFrom().getId()); //$NON-NLS-1$
		thisEdgeElement.setAttribute("to", thisEdge.getTo().getId()); //$NON-NLS-1$
		Integer thisStyle = thisEdge.getStyle();
		if (thisStyle != null)
			setStyleAttributes(thisEdgeElement, thisStyle);
		Color thisColor = thisEdge.getColor();
		if (thisColor != null)
			thisEdgeElement.setAttribute("color", Utils.colorToString(thisColor)); //$NON-NLS-1$

		// label
		String thisLabel = thisEdge.getLabel();
		if (thisLabel != null && !thisLabel.isEmpty())
			thisEdgeElement.appendChild(makeLabel(thisDocument, thisLabel));

		// img
		String thisImage = thisEdge.getImageFile();
		if (thisImage != null)
			thisEdgeElement.appendChild(makeImage(thisDocument, thisImage));

		return thisEdgeElement;
	}

	/**
	 * Make label element
	 * 
	 * @param thisDocument
	 *        document
	 * @param thisString
	 *        label
	 * @return label element
	 */
	static private Element makeLabel(Document thisDocument, String thisString)
	{
		Element thisElement = thisDocument.createElement("label"); //$NON-NLS-1$
		Text thisText = thisDocument.createTextNode(thisString);
		thisElement.appendChild(thisText);
		return thisElement;
	}

	/**
	 * Make content element
	 * 
	 * @param thisDocument
	 *        document
	 * @param thisString
	 *        content
	 * @return content element
	 */
	static private Element makeContent(Document thisDocument, String thisString)
	{
		Element thisContentElement = thisDocument.createElement("content"); //$NON-NLS-1$
		CDATASection thisContentCData = thisDocument.createCDATASection(thisString);
		thisContentElement.appendChild(thisContentCData);
		return thisContentElement;
	}

	/**
	 * Make image element
	 * 
	 * @param thisDocument
	 *        document
	 * @param thisImageSrc
	 *        image source
	 * @return image element
	 */
	static public Element makeImage(Document thisDocument, String thisImageSrc)
	{
		Element thisElement = thisDocument.createElement("img"); //$NON-NLS-1$
		thisElement.setAttribute("src", thisImageSrc); //$NON-NLS-1$
		return thisElement;
	}

	/**
	 * Make link element
	 * 
	 * @param thisDocument
	 *        document
	 * @param thisHref
	 *        link href
	 * @return link element
	 */
	static private Element makeLink(Document thisDocument, String thisHref, String thisTarget)
	{
		Element thisElement = thisDocument.createElement("a"); //$NON-NLS-1$
		thisElement.setAttribute("href", thisHref); //$NON-NLS-1$
		if (thisTarget != null && !thisTarget.isEmpty())
			thisElement.setAttribute("target", thisTarget); //$NON-NLS-1$
		return thisElement;
	}

	/**
	 * Make mountpoint element
	 * 
	 * @param thisDocument
	 *        document
	 * @param thisMountPoint
	 *        mountpoint
	 * @return mountpoint element
	 */
	static private Element makeMountPoint(Document thisDocument, MountPoint.Mounting thisMountPoint)
	{
		Element thisMountPointElement = thisDocument.createElement("mountpoint"); //$NON-NLS-1$

		// attributes
		if (thisMountPoint.now != null)
			thisMountPointElement.setAttribute("now", thisMountPoint.now.toString()); //$NON-NLS-1$

		// a
		Element thisAElement = thisDocument.createElement("a"); //$NON-NLS-1$
		thisAElement.setAttribute("href", thisMountPoint.theURL); //$NON-NLS-1$
		thisMountPointElement.appendChild(thisAElement);
		return thisMountPointElement;
	}

	/**
	 * Make menuitem element
	 * 
	 * @param thisDocument
	 *        document
	 * @param thisMenuItem
	 *        menuitem
	 * @return menuitem
	 */
	static private Element makeMenuItem(Document thisDocument, MenuItem thisMenuItem)
	{
		Element thisMenuItemElement = thisDocument.createElement("menuitem"); //$NON-NLS-1$

		// attributes
		// action (goto|search|focus), match-target, match-scope, match-mode (equals|startswith|includes)
		String[] theseStrings = Utils.toStrings(thisMenuItem);
		if (theseStrings[0] != null)
			thisMenuItemElement.setAttribute("action", theseStrings[0]); //$NON-NLS-1$
		if (theseStrings[1] != null)
			thisMenuItemElement.setAttribute("match-scope", theseStrings[1]); //$NON-NLS-1$
		if (theseStrings[2] != null)
			thisMenuItemElement.setAttribute("match-mode", theseStrings[2]); //$NON-NLS-1$
		if (thisMenuItem.theMatchTarget != null && !thisMenuItem.theMatchTarget.isEmpty())
			thisMenuItemElement.setAttribute("match-target", thisMenuItem.theMatchTarget); //$NON-NLS-1$

		// label
		Element thisMenuItemLabelElement = thisDocument.createElement("label"); //$NON-NLS-1$
		thisMenuItemLabelElement.appendChild(thisDocument.createTextNode(thisMenuItem.theLabel));
		thisMenuItemElement.appendChild(thisMenuItemLabelElement);

		// a
		if (thisMenuItem.theLink != null && !thisMenuItem.theLink.isEmpty())
		{
			Element thisAElement = thisDocument.createElement("a"); //$NON-NLS-1$
			thisAElement.setAttribute("href", thisMenuItem.theLink); //$NON-NLS-1$
			thisMenuItemElement.appendChild(thisAElement);
		}

		return thisMenuItemElement;
	}

	/**
	 * Set style attributes
	 * 
	 * @param thisElement
	 *        element
	 * @param thisStyle
	 *        style
	 */
	static private void setStyleAttributes(Element thisElement, Integer thisStyle)
	{
		if (thisStyle != null)
		{
			String thisString = Utils.toString(thisStyle, Utils.StyleComponent.STROKE);
			if (thisString != null && !thisString.isEmpty())
				thisElement.setAttribute("stroke", thisString); //$NON-NLS-1$
			thisString = Utils.toString(thisStyle, Utils.StyleComponent.FROMTERMINATOR);
			if (thisString != null && !thisString.isEmpty())
				thisElement.setAttribute("fromterminator", thisString); //$NON-NLS-1$
			thisString = Utils.toString(thisStyle, Utils.StyleComponent.TOTERMINATOR);
			if (thisString != null && !thisString.isEmpty())
				thisElement.setAttribute("toterminator", thisString); //$NON-NLS-1$
			if ((thisStyle & IEdge.HIDDEN) != 0)
				thisElement.setAttribute("hidden", "true"); //$NON-NLS-1$ //$NON-NLS-2$
		}
	}

	/**
	 * Set attributes
	 * 
	 * @param thisElement
	 *        element
	 * @param theseAttributes
	 *        attributes
	 */
	static private void setAttributes(Element thisElement, Map<String, Object> theseAttributes)
	{
		for (String thisKey : theseAttributes.keySet())
		{
			Object thisValue = theseAttributes.get(thisKey);
			if (thisValue != null)
			{
				String thisString = thisValue.toString();
				if (thisValue instanceof Color)
					thisString = Utils.colorToString((Color) thisValue);
				thisElement.setAttribute(thisKey, thisString);
			}
		}
		theseAttributes.clear();
	}
}
